- Published on
Create a Random Quote Machine in JavaScript | freeCodeCamp
- Authors
- Name
- Alberto Montalesi
This project is based on a project from the freeCodeCamp Curriculum that you can find here.
The goals of this project is to be able to fetch random quote from a public api and display them on our page.
It's a simple task suitable for beginners that will help you learn how to interact with public API to fetch and display data.
The Task
- Difficulty: Beginner
- Tools: Text Editor of your choice
- Duration: < 1 hour
Creating the HTML for the Random Quote Machine
Let's first start by creating the structure of our project. Open your text editor and create an index.html
file.
The project is very simple, we only need a container where to display quotes, a button to fetch them and a twitter button to share them.
First add this to the head of your html file:
<a
rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"
/>
We are importing font awesome as we are going to use one of their icon for our twitter button.
Next, let's create the structure of our project which will be like this:
- container
div
that will change color every time we get a new quote- an inner
div
- a
div
for the quote - a
div
for the two buttons
- a
- an inner
<div class="page-content randomBgColor">
<div id="quote-box">
<div id="text">
<i class="fa fa-quote-left randomTxtColor" aria-hidden="true" />
<span id="quoteText" class="randomTxtColor" />
<br />
<span id="quoteAuthor" class="randomTxtColor" />
</div>
<div id="buttonBox">
<a
id="twitter-share-button"
target="blank"
href="twitter.com/intent/tweet"
data-text="custom share text"
>
<i class="fa fa-twitter randomBgColor" aria-hidden="true"></i>
</a>
<button id="new-quote" class="cta randomBgColor">New Quote</button>
</div>
</div>
</div>
As you can see the structure is quite simple, the classes i have added are mostly for styling and for our JavaScript to target the correct elements in the DOM.
Styling the Random Quote Machine with CSS
You can skip this part if you want the Random Quote Machine to look differently but if you are interested in achieving the same result as mine, here is the style that I have used.
.randomBgColor {
transition: background-color 1s ease;
}
.randomTxtColor {
transition: color 1s ease;
}
#quote-box {
max-width: 400px;
width: 100%;
min-height: 200px;
height: auto;
margin: 10% auto 10% auto;
background-color: white;
padding: 10px;
padding-bottom: 20px;
border-radius: 5%;
}
#text {
padding: 25px;
}
.fa-quote-left {
font-size: 55px;
padding-right: 15px;
display: inline-block;
width: auto;
}
#quoteText {
font-size: 25px;
text-align: center;
width: 70%;
}
#author {
font-size: 16px;
text-align: right;
display: block;
width: 100%;
}
#buttonBox {
height: 50px;
padding: 15px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-content: center;
}
.fa-twitter {
font-size: 40px;
padding: 5px 5px 0 5px;
color: white;
}
#new-quote {
padding: 0 10px;
cursor: pointer;
color: white;
font-weight: bold;
border-radius: 5px;
border: none;
}
#new-quote:hover {
opacity: 0.5;
}
.fa-twitter {
border-radius: 20%;
border: none;
}
#credit {
display: block;
margin: 0 auto;
width: 150px;
height: 15px;
color: white;
}
#credit a,
#credit a:hover {
color: inherit;
text-decoration: none;
}
Adding JavaScript to the Random Quote Machine
This is the most important part of the tutorial, we will learn how to fetch data from the Forismatic API and display it in our page.
To start, let's define the base URL where to make our API calls at the top of our JavaScript file:
const url =
'https://api.forismatic.com/api/1.0/?method=getQuote&format=jsonp&lang=en&jsonp=displayQuote'
For simplicity we will create two function, one to get the quote from the API and one to change the color of our container div
.
Let's first start with the easiest one, changing color:
const colorRandomizer = () => {
const myColors = ['#3498db', '#2ecc71', '#9b59b6', '#e74c3c', '#f1c40f'] //array of colors
const randomNum = Math.floor(Math.random() * myColors.length) //generate random number
const randomColor = myColors[randomNum]
// modify bg and txt color with my random color
// get the DOM element
const bg = document.querySelector('.randomBgColor')
// set the css attribute on it
bg.setAttribute('style', `background-color: ${randomColor}`)
const textElements = document.querySelectorAll('.randomTxtColor')
for (const text of textElements) {
// we have multiple elements where we want to change the text colors
text.setAttribute('style', `color: ${randomColor}`)
}
}
myColors
is an array of colors and randomNum
will be used to generate and store a random index of the myColors
array. randomColor
is then applied to both our container div
and our quote text with .setAttribute
.
Now let's move on to fetching data from the API. There are multiple ways we can achieve that, here we are going to look at fetching data via JSONP which will allow us to quickly overcome the problem of getting content from two different origins (in this case our web page and the Forsimatic API). You can read more about the same-origin policy here.
The same-origin policy is a security mechanism that will restrict usage of resources coming from a different origin. To overcome this, we will inject a script
tag in our html document and call that function when making a JSONP ('JSON with padding') request.
The response from our JSONP request will contain the JSON response from the API and a callback function to which we can pass any argument we want.
This type of request can be convenient but it also poses issues when the API server get's compromised as it may lead to malicious code being injected in your page so be careful and use it only for trivial projects such as this one.
const getQuote = (data) => {
// 1) here we are creating the callback function that we will pass to the JSONP request
const callbackName = 'displayQuote'
window[callbackName] = function (data) {
delete window[callbackName]
document.body.removeChild(script)
callback(data)
}
// 2) we are injecting the script tag into our HTML
const script = document.createElement('script')
script.src = url + (url.indexOf('?') >= 0 ? '&' : '?') + 'callback=' + callbackName
document.body.appendChild(script)
}
As you saw, we called our callback function displayQuote
(const callbackName = 'displayQuote'
) so let's define it. This will be the function called by our JSONP request and it will take care of passing the text and author of the quote to our HTML elements.
const displayQuote = (data) => {
const currentQuote = data.quoteText
const currentAuthor = data.quoteAuthor
//add them to my html
const text = document.querySelector('#quoteText')
text.innerHTML = currentQuote
const author = document.querySelector('#quoteAuthor')
author.innerHTML = `- ${currentAuthor}`
const twitter = document.querySelector('#twitter-share-button')
twitter.setAttribute(
'href',
'https://twitter.com/intent/tweet?hashtags=quotes,Fcc&related=freecodecamp&text=' +
encodeURIComponent('"' + currentQuote + '" ' + currentAuthor)
)
}
We are saving the author and text of the quote in two variables and adding it to our HTML element by using .innerHTML
. We are also passing those two values to the Twitter url so that when users press the button, the tweet will be pre-filled with our quote.
Awesome, we are just a few steps away from finishing this project.
Currently, if we try to click our button, nothing happens so let's fix this.
document.querySelector('#new-quote').addEventListener('click', () => {
getQuote(displayQuote)
})
What we did here was to add an event listener to our button
which will fire the getQuote
function with the displayQuote
as a callback.
The last step to do now is to make it so that when the user visits the page for the first time, a quote will be fetched.
document.addEventListener('DOMContentLoaded', () => {
getQuote(displayQuote);
}
Great, we are waiting for the page to load and then fire our getQuote
function. This way, when you land on the page, a quote will already be there without having to press the button
.
If you followed the instructions you should now have something that looks like the following: